/*
 * Copyright (C) 2018 Alyssa Rosenzweig <alyssa@rosenzweig.io>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sub license,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <assert.h>
#include "test-util-3d.h"

/* Include image loading library */
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

struct image {
	uint8_t *data;
	int width;
	int height;
	int n;
};

static EGLint const config_attribute_list[] = {
	EGL_RED_SIZE, 8,
	EGL_GREEN_SIZE, 8,
	EGL_BLUE_SIZE, 8,
	EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
	EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
	EGL_DEPTH_SIZE, 8,
	EGL_NONE
};

static const EGLint context_attribute_list[] = {
	EGL_CONTEXT_CLIENT_VERSION, 2,
	EGL_NONE
};

static EGLDisplay display;
static EGLConfig config;
static EGLint num_config;
static EGLContext context;
static GLuint program;
const char *vertex_shader_source =
		"attribute vec4 aPosition;    \n"
		"attribute vec4 aColor;       \n"
		"uniform sampler2D depthMap;       \n"

		"uniform float angle;\n"
		"uniform float sign;\n"
		"                             \n"
		"varying vec4 vColor;         \n"
		"                             \n"
		"void main()                  \n"
		"{                            \n"
		"    vColor = aColor.xyww + aPosition.z*vec4(0.0,0.0,0.02,0.0);         \n"
		//"    vColor = vec4(aPosition.z * 0.01);\n"
		"    float x = aPosition.x;\n"
		"    float y = aPosition.y;\n"
		"    gl_Position = vec4(x, y*cos(angle) - aPosition.z*sin(angle), 1.0, 1.0); \n"
		"}                            \n";
const char *fragment_shader_source =
		"precision mediump float;     \n"
		"                             \n"
		"varying vec4 vColor;         \n"
		"uniform sampler2D uTexture;         \n"
		"uniform sampler2D depthMap;       \n"
		"                             \n"
		"void main()                  \n"
		"{                            \n"
		"    gl_FragColor = texture2D(uTexture, vColor.xy);"
		//"    gl_FragColor = vec4(z * 4.0);   \n"
		 //"    gl_FragColor = vColor;\n"
		"}                            \n";

/* Point cloud triangulation implementation:
 *
 * 1. Generate wxh triangular grid
 * 2. Extrude each point based on the value in the depth map
 * 3. Perform rotation and orthogonal projection in vertex shader
 * 4. Perform simple texturing in fragment shader
 */

struct forward_grid {
	int width, height;

	GLfloat *vVertices;
	GLfloat *vColors;

	unsigned int *indices;
	int index_count;
};

static struct forward_grid
generate_grid(struct image *depth_map, int W, int H)
{
	struct forward_grid grid = {
		.width = W,
		.height = H
	};

	/* Allocate room for the vertices/attributes/indices */
	grid.vVertices = calloc(W * H * 3, sizeof(GLfloat));
	grid.vColors = calloc(W * H * 4, sizeof(GLfloat));
	grid.indices = calloc(W * H * 6, sizeof(*grid.indices));

	/* We want to subdivide [0, 0] to [w/h, 1] in wxh pieces */

	float step_size_x = (((float) W) / ((float) H)) / ((float) (W - 1));
	float step_size_y = (1.0) / ((float) (H - 1));

	for (int step_y = 0; step_y < H; ++step_y) {
		float y = step_y * step_size_y;

		for (int step_x = 0; step_x < W; ++step_x) {
			float x = step_x * step_size_x;

			/* Look up z in depth map */
			int depth_x = (int) (((float) step_x / W) * depth_map->width);
			int depth_y = (int) (((float) step_y / H) * depth_map->height);
			int z8 = depth_map->data[(depth_y*depth_map->width + depth_x)*4];

			/* Normalize z to the usual space */
			float z = ((float) z8) / depth_map->width;

			/* This grid section is a rect from (x, y) to
			 * (x+step_size, y + step_size) */

			int vertex_number = step_y*W + step_x;

			grid.vVertices[vertex_number*3 + 0] = x;
			grid.vVertices[vertex_number*3 + 1] = y;
			grid.vVertices[vertex_number*3 + 2] = z;

			/* Set some attributes */
			grid.vColors[vertex_number*4 + 0] = ((float) step_x) / (W - 1);
			grid.vColors[vertex_number*4 + 1] = ((float) step_y) / (H - 1);
		}
	}

	/* Form indexed buffer from the subdivied vertices. The scheme is to
	 * draw quads connecting adjacent vertices, and split those quads into
	 * triangles. */

	int index_start = 0;

	for (int y = 0; y < (H - 1); ++y) {
		for (int x = 0; x < (W - 1); ++x) {
			/* First, ensure the triangle actually exists */

			if ((grid.vVertices[(y*W + x)*3 + 2] == 0))
				continue;

			/* Connect (x, y) (x + 1, y) (x, y + 1) */
			grid.indices[index_start + 0] = (y + 0)*W + (x + 0);
			grid.indices[index_start + 1] = (y + 0)*W + (x + 1);
			grid.indices[index_start + 2] = (y + 1)*W + (x + 0);

			/* Connect (x + 1, y + 1) (x + 1, y) (x, y + 1).
			 * XXX: Winding order? */

			grid.indices[index_start + 3] = (y + 0)*W + (x + 1);
			grid.indices[index_start + 4] = (y + 1)*W + (x + 1);
			grid.indices[index_start + 5] = (y + 1)*W + (x + 0);

			/* Advance index buffer */
			index_start += 6;
		}
	}

	grid.index_count = index_start;

	/* Send the grid off to the GPU */

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, grid.vVertices);
	glEnableVertexAttribArray(0);

	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, grid.vColors);
	glEnableVertexAttribArray(1);

	return grid;
}

static void
draw_grid(struct forward_grid *grid)
{
	glDrawElements(GL_TRIANGLES, grid->index_count, GL_UNSIGNED_INT, grid->indices);
}

/* Texture related boilerplate */

static void
init_texture(struct image *img, int number)
{
	GLuint textures[2], texture_handle;

	glGenTextures(1, &textures);

	glActiveTexture(GL_TEXTURE0 + number);
	glBindTexture(GL_TEXTURE_2D, textures[0]);

	int mode = (img->n == 4) ? GL_RGBA : GL_LUMINANCE;

	glTexImage2D(
	   	GL_TEXTURE_2D, 0, mode,
	   	img->width, img->height, 0,
	   	mode, GL_UNSIGNED_BYTE, img->data);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	texture_handle = glGetUniformLocation(program, number ? "depthMap" : "uTexture");
	glUniform1i(texture_handle, number);
}

/* Depth map generation:
 * The idea is to choose an axis running through the object,
 * and measure perpendicular distance away from that axis.
 */

/* First, consider perpendicular distance "pixel counting". 
 *
 * In particular, we limit to the special case where the axis is pointing
 * completely down. This is ideal to avoid roundoff error which would greatly
 * complicate the routine and later stages. TODO: Rotate the image
 * ahead-of-time.
 *
 * Thus, it is only necessary to know the starting point to count from.
 * Counting looks for pixels in the strictly perpendicular direction; that is,
 * directly to the left or right. At the moment, we assume considerable
 * symmetry and count to the right. TODO: Is this correct? */

static int
count_pixels(struct image *img, int start_x, int start_y)
{
	int distance = 0;

	uint32_t *data32 = (uint32_t *) img->data;

	/* Iterate perpendicularly */

	for (int x = start_x; x < img->width; ++x) {
		uint32_t color = data32[start_y * img->width + x];
		uint8_t alpha = color >> 24;

		/* If there's nothing here, the edge has been found */
		if (alpha == 0) break;

		++distance;
	}

	return distance;
}

#define MIN(a,b) ((a>b)?(b):(a))

/* Next, we generate the 8-bit depth map.
 *
 * We first count pixels along the vertical axis for easy access.
 *
 * We then assign each pixel a depth value based on the depth function and this
 * count */

static struct image
generate_depth_map(struct image *src, int start_x, int start_y)
{
	/* Allocate room for the image and perpendicular lengths */

	struct image dst = {
		.data = calloc(src->width * src->height, 4),
		.width = src->width,
		.height = src->height,
		.n = 4,
	};

	int *lengths = malloc(sizeof(int) * src->height);

	/* Count pixels */

	for (int y = 0; y < src->height; ++y) {
		lengths[y] = count_pixels(src, start_x, y);
	}

	/* Generate depth map */
	uint32_t *data32 = (uint32_t *) dst.data;

	/* Off-by-one on both ends to make sure it is capped. The y cap is a
	 * function of the algorithm. The x cap is just a bug in this initial
	 * implementation XXX */

	for (int y = 1; y < dst.height - 1; ++y) {
		for (int x = 1; x < dst.width - 1; ++x) {
			float length = lengths[y];
			float coord = x - start_x;
			float depth_det = length*length - coord*coord;

			/* Assume zero depth => nonexistent */
			uint8_t udepth = 0;

			/* If determinant is negative, no depth. If positive,
			 * there is something here; take the square root and
			 * store that. */

			if (depth_det >= 0) {
				float depth = sqrtf(depth_det);
				int d = depth;

				data32[(y * dst.width) + x] = MIN(d, 255);
			}
		}
	}

	return dst;
}

int main()
{
	GLint width, height;
	EGLSurface surface;

	display = get_display();

	/* get an appropriate EGL frame buffer configuration */
	eglChooseConfig(display, config_attribute_list, &config, 1, &num_config);

	/* create an EGL rendering context */
	context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attribute_list);

	surface = make_window(display, config, 2048, 1280);

	eglQuerySurface(display, surface, EGL_WIDTH, &width);
	eglQuerySurface(display, surface, EGL_HEIGHT, &height);

	/* connect the context to the surface */
	eglMakeCurrent(display, surface, surface, context);

	program = get_program(vertex_shader_source, fragment_shader_source);

	glBindAttribLocation(program, 0, "aPosition");
	glBindAttribLocation(program, 1, "aColor");

	link_program(program);

	glViewport(0, 0, width, height);

	/* Load the image */

	struct image img;

	img.data = stbi_load("pencil.png", &img.width, &img.height, &img.n, 0);
	assert(img.data != NULL);
	assert(img.n == 4);

	/* XXX: We probably want a better way to input these */
	int start_x = img.width >> 1;
	int start_y = 0;

	struct image depth_map = generate_depth_map(&img, start_x, start_y);

	init_texture(&img, 0);
	init_texture(&depth_map, 1);

	/* Clean up */
	stbi_image_free(img.data);

	/* Generate the forward mapping grid */
	struct forward_grid grid = generate_grid(&depth_map, img.width >> 3, img.height >> 3);

	/* Setup the angle */
	int u_angle = glGetUniformLocation(program, "angle");
	int u_sign = glGetUniformLocation(program, "sign");
	float angle = 0.1f;

	glEnable(GL_CULL_FACE);

	for (;;) {
		glClearColor(0.5, 0.0, 0.0, 1.0);
		glClear(GL_COLOR_BUFFER_BIT);

		glUniform1f(u_angle, angle);

		glFrontFace(GL_CCW);
		glUniform1f(u_sign, 1.0);
		draw_grid(&grid);

#if 0
		glUniform1f(u_angle, angle + 180);

		glFrontFace(GL_CW);
		glUniform1f(u_sign, -1.0);
		draw_grid(&grid);
#endif

		eglSwapBuffers(display, surface);
		glFlush();

		angle += (3.14159f/180.0f);
		//angle += 1.0/360.0f;
		//printf("%f\n", angle*180.0f/3.14159f);
	}

	eglDestroySurface(display, surface);

	eglTerminate(display);

	return 0;
}
